home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / html.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  49.3 KB  |  1,661 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const Cc = Components.classes;
  9. const Ci = Components.interfaces;
  10. const nsIDOMNodeFilter = Ci.nsIDOMNodeFilter;
  11.  
  12. const SHOW_ALL = nsIDOMNodeFilter.SHOW_ALL;
  13.  
  14. const MODIFICATION = MutationEvent.MODIFICATION;
  15. const ADDITION = MutationEvent.ADDITION;
  16. const REMOVAL = MutationEvent.REMOVAL;
  17.  
  18. var AttrTag =
  19.     SPAN({class: "nodeAttr editGroup"},
  20.         " ", SPAN({class: "nodeName editable"}, "$attr.nodeName"), "="",
  21.         SPAN({class: "nodeValue editable"}, "$attr.nodeValue"), """
  22.     );
  23.  
  24. // ************************************************************************************************
  25.  
  26. Firebug.HTMLPanel = function() {};
  27.  
  28. Firebug.HTMLPanel.prototype = extend(Firebug.Panel,
  29. {
  30.     toggleEditing: function()
  31.     {
  32.         if (this.editing)
  33.             Firebug.Editor.stopEditing();
  34.         else
  35.             this.editNode(this.selection);
  36.     },
  37.  
  38.     resetSearch: function()
  39.     {
  40.         delete this.lastSearch;
  41.     },
  42.  
  43.     selectNext: function()
  44.     {
  45.         var objectBox = this.ioBox.createObjectBox(this.selection);
  46.         var next = this.ioBox.getNextObjectBox(objectBox);
  47.         if (next)
  48.         {
  49.             this.select(next.repObject);
  50.  
  51.             if (Firebug.Inspector.inspecting)
  52.                 Firebug.Inspector.inspectNode(next.repObject);
  53.  
  54.         }
  55.     },
  56.  
  57.     selectPrevious: function()
  58.     {
  59.         var objectBox = this.ioBox.createObjectBox(this.selection);
  60.         var previous = this.ioBox.getPreviousObjectBox(objectBox);
  61.         if (previous)
  62.         {
  63.             this.select(previous.repObject);
  64.  
  65.             if (Firebug.Inspector.inspecting)
  66.                 Firebug.Inspector.inspectNode(previous.repObject);
  67.         }
  68.     },
  69.  
  70.     selectNodeBy: function(dir)
  71.     {
  72.         if (dir == "up")
  73.             this.selectPrevious();
  74.         else if (dir == "down")
  75.             this.selectNext();
  76.         else if (dir == "left")
  77.             this.ioBox.contractObject(this.selection);
  78.         else if (dir == "right")
  79.             this.ioBox.expandObject(this.selection);
  80.     },
  81.  
  82.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  83.  
  84.     editNewAttribute: function(elt)
  85.     {
  86.         var objectNodeBox = this.ioBox.findObjectBox(elt);
  87.         if (objectNodeBox)
  88.         {
  89.             var labelBox = objectNodeBox.firstChild.lastChild;
  90.             var bracketBox = getChildByClass(labelBox, "nodeBracket");
  91.             Firebug.Editor.insertRow(bracketBox, "before");
  92.         }
  93.     },
  94.  
  95.     editAttribute: function(elt, attrName)
  96.     {
  97.         var objectNodeBox = this.ioBox.findObjectBox(elt);
  98.         if (objectNodeBox)
  99.         {
  100.             var attrBox = findNodeAttrBox(objectNodeBox, attrName);
  101.             if (attrBox)
  102.             {
  103.                 var attrValueBox = attrBox.childNodes[3];
  104.                 var value = elt.getAttribute(attrName);
  105.                 Firebug.Editor.startEditing(attrValueBox, value);
  106.             }
  107.         }
  108.     },
  109.  
  110.     deleteAttribute: function(elt, attrName)
  111.     {
  112.         elt.removeAttribute(attrName);
  113.  
  114.         //this.markChange();
  115.     },
  116.  
  117.     editNode: function(node)
  118.     {
  119.         if ( nonEditableTags.hasOwnProperty(node.localName) )
  120.             return;
  121.  
  122.         var objectNodeBox = this.ioBox.findObjectBox(node);
  123.         if (objectNodeBox)
  124.         {
  125.             if (!this.htmlEditor)
  126.                 this.htmlEditor = new HTMLEditor(this.document);
  127.  
  128.             this.htmlEditor.innerEditMode = node.localName in innerEditableTags;
  129.  
  130.             var html = this.htmlEditor.innerEditMode ? node.innerHTML : getElementXML(node);
  131.             Firebug.Editor.startEditing(objectNodeBox, html, this.htmlEditor);
  132.         }
  133.     },
  134.  
  135.     deleteNode: function(node)
  136.     {
  137.         node.parentNode.removeChild(node);
  138.  
  139.         //this.markChange();
  140.     },
  141.  
  142.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  143.  
  144.     getElementSourceText: function(node)
  145.     {
  146.         if (this.sourceElements)
  147.         {
  148.             var index = this.sourceElementNodes.indexOf(node);
  149.             if (index != -1)
  150.                 return this.sourceElements[index];
  151.         }
  152.  
  153.         var lines;
  154.  
  155.         var url = getSourceHref(node);
  156.         if (url)
  157.             lines = this.context.sourceCache.load(url);
  158.         else
  159.         {
  160.             var text = getSourceText(node);
  161.             lines = text.split(/\r\n|\r|\n/);
  162.         }
  163.  
  164.         var sourceElt = new SourceText(lines, node);
  165.  
  166.         if (!this.sourceElements)
  167.         {
  168.             this.sourceElements =  [sourceElt];
  169.             this.sourceElementNodes = [node];
  170.         }
  171.         else
  172.         {
  173.             this.sourceElements.push(sourceElt);
  174.             this.sourceElementNodes.push(node);
  175.         }
  176.  
  177.         return sourceElt;
  178.     },
  179.  
  180.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  181.  
  182.     mutateAttr: function(target, attrChange, attrName, attrValue)
  183.     {
  184.         // Every time the user scrolls we get this pointless mutation event, which
  185.         // is only bad for performance
  186.         if (attrName == "curpos")
  187.             return;
  188.         this.markChange();
  189.  
  190.         var objectNodeBox = Firebug.scrollToMutations || Firebug.expandMutations
  191.             ? this.ioBox.createObjectBox(target)
  192.             : this.ioBox.findObjectBox(target);
  193.         if (!objectNodeBox)
  194.             return;
  195.  
  196.         if (isVisible(objectNodeBox.repObject))
  197.             removeClass(objectNodeBox, "nodeHidden");
  198.         else
  199.             setClass(objectNodeBox, "nodeHidden");
  200.  
  201.         if (attrChange == MODIFICATION || attrChange == ADDITION)
  202.         {
  203.             var nodeAttr = findNodeAttrBox(objectNodeBox, attrName);
  204.             if (nodeAttr && nodeAttr.childNodes.length > 3)
  205.             {
  206.                 var attrValueBox = nodeAttr.childNodes[3];
  207.                 var attrValueText = nodeAttr.childNodes[3].firstChild;
  208.                 if (attrValueText)
  209.                     attrValueText.nodeValue = attrValue;
  210.  
  211.                 this.highlightMutation(attrValueBox, objectNodeBox, "mutated");
  212.             }
  213.             else
  214.             {
  215.                 var attr = target.getAttributeNode(attrName);
  216.                 if (attr)
  217.                 {
  218.                     var nodeAttr = Firebug.HTMLPanel.AttrNode.tag.replace({attr: attr},
  219.                             this.document);
  220.  
  221.                     var labelBox = objectNodeBox.firstChild.lastChild;
  222.                     var bracketBox = getChildByClass(labelBox, "nodeBracket");
  223.                     labelBox.insertBefore(nodeAttr, bracketBox);
  224.  
  225.                     this.highlightMutation(nodeAttr, objectNodeBox, "mutated");
  226.                 }
  227.             }
  228.         }
  229.         else if (attrChange == REMOVAL)
  230.         {
  231.             var nodeAttr = findNodeAttrBox(objectNodeBox, attrName);
  232.             if (nodeAttr)
  233.             {
  234.                 nodeAttr.parentNode.removeChild(nodeAttr);
  235.  
  236.                 this.highlightMutation(objectNodeBox, objectNodeBox, "mutated");
  237.             }
  238.         }
  239.     },
  240.  
  241.     mutateText: function(target, parent, textValue)
  242.     {
  243.         this.markChange();
  244.  
  245.         var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations
  246.             ? this.ioBox.createObjectBox(parent)
  247.             : this.ioBox.findObjectBox(parent);
  248.         if (!parentNodeBox)
  249.             return;
  250.  
  251.         if (!Firebug.showFullTextNodes)
  252.             textValue = cropString(textValue, 50);
  253.  
  254.         var parentTag = getNodeBoxTag(parentNodeBox);
  255.         if (parentTag == Firebug.HTMLPanel.TextElement.tag)
  256.         {
  257.             var nodeText = getTextElementTextBox(parentNodeBox);
  258.             if (!nodeText.firstChild)
  259.                 return;
  260.  
  261.             nodeText.firstChild.nodeValue = textValue;
  262.  
  263.             this.highlightMutation(nodeText, parentNodeBox, "mutated");
  264.         }
  265.         else
  266.         {
  267.             var childBox = this.ioBox.getChildObjectBox(parentNodeBox);
  268.             if (!childBox)
  269.                 return;
  270.  
  271.             var textNodeBox = this.ioBox.findChildObjectBox(childBox, target);
  272.             if (textNodeBox)
  273.             {
  274.                 textNodeBox.firstChild.lastChild.nodeValue = textValue;
  275.  
  276.                 this.highlightMutation(textNodeBox, parentNodeBox, "mutated");
  277.             }
  278.         }
  279.     },
  280.  
  281.     mutateNode: function(target, parent, nextSibling, removal)
  282.     {
  283.         this.markChange();  // This invalidates the panels for every mutate
  284.  
  285.         var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations
  286.             ? this.ioBox.createObjectBox(parent)
  287.             : this.ioBox.findObjectBox(parent);
  288.  
  289.         if (!parentNodeBox)
  290.             return;
  291.  
  292.         if (!Firebug.showWhitespaceNodes && this.isWhitespaceText(target))
  293.             return;
  294.  
  295.         var newParentTag = getNodeTag(parent);
  296.         var oldParentTag = getNodeBoxTag(parentNodeBox);
  297.  
  298.         if (newParentTag == oldParentTag)
  299.         {
  300.             if (parentNodeBox.populated)
  301.             {
  302.                 if (removal)
  303.                 {
  304.                     this.ioBox.removeChildBox(parentNodeBox, target);
  305.  
  306.                     this.highlightMutation(parentNodeBox, parentNodeBox, "mutated");
  307.                 }
  308.                 else
  309.                 {
  310.                     var objectBox = nextSibling
  311.                         ? this.ioBox.insertChildBoxBefore(parentNodeBox, target, nextSibling)
  312.                         : this.ioBox.appendChildBox(parentNodeBox, target);
  313.  
  314.                     this.highlightMutation(objectBox, objectBox, "mutated");
  315.                 }
  316.             }
  317.             else
  318.             {
  319.                 var newParentNodeBox = newParentTag.replace({object: parent}, this.document);
  320.                 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox);
  321.  
  322.                 this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated");
  323.  
  324.                 if (Firebug.scrollToMutations || Firebug.expandMutations)
  325.                 {
  326.                     var objectBox = this.ioBox.createObjectBox(target);
  327.                     this.highlightMutation(objectBox, objectBox, "mutated");
  328.                 }
  329.             }
  330.         }
  331.         else
  332.         {
  333.             var newParentNodeBox = newParentTag.replace({object: parent}, this.document);
  334.             if (parentNodeBox.parentNode)
  335.                 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox);
  336.  
  337.             if (hasClass(parentNodeBox, "open"))
  338.                 this.ioBox.toggleObjectBox(newParentNodeBox, true);
  339.  
  340.             if (this.selection && (!this.selection.parentNode || parent == this.selection))
  341.                 this.ioBox.select(parent, true);
  342.  
  343.             this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated");
  344.         }
  345.     },
  346.  
  347.     highlightMutation: function(elt, objectBox, type)
  348.     {
  349.         if (!elt)
  350.             return;
  351.  
  352.         if (Firebug.scrollToMutations || Firebug.expandMutations)
  353.         {
  354.             if (this.context.mutationTimeout)
  355.             {
  356.                 this.context.clearTimeout(this.context.mutationTimeout);
  357.                 delete this.context.mutationTimeout;
  358.             }
  359.  
  360.             var ioBox = this.ioBox;
  361.             var panelNode = this.panelNode;
  362.  
  363.             this.context.mutationTimeout = this.context.setTimeout(function()
  364.             {
  365.                 ioBox.openObjectBox(objectBox);
  366.  
  367.                 if (Firebug.scrollToMutations)
  368.                     scrollIntoCenterView(objectBox, panelNode);
  369.             }, 200);
  370.         }
  371.  
  372.         if (Firebug.highlightMutations)
  373.             setClassTimed(elt, type, this.context);
  374.     },
  375.  
  376.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  377.     // SourceBox proxy
  378.  
  379.     createObjectBox: function(object, isRoot)
  380.     {
  381.         var tag = getNodeTag(object);
  382.         if (tag)
  383.             return tag.replace({object: object}, this.document);
  384.     },
  385.  
  386.     getParentObject: function(node)
  387.     {
  388.         if (node instanceof SourceText)
  389.             return node.owner;
  390.  
  391.         if (this.rootElement && node == this.rootElement)  // this.rootElement is never set
  392.             return null;
  393.  
  394.         var parentNode = node ? node.parentNode : null;
  395.         if (parentNode)
  396.             if (parentNode.nodeType == 9)
  397.             {
  398.                 if (parentNode.defaultView)
  399.                     return parentNode.defaultView.frameElement;
  400.                 else
  401.                 {
  402.                 }
  403.             }
  404.             else
  405.                 return parentNode;
  406.         else
  407.             if (node && node.nodeType == 9) // document type
  408.             {
  409.                 var embeddingFrame = node.defaultView.frameElement;
  410.                 if (embeddingFrame)
  411.                     return embeddingFrame.parentNode;
  412.                 else
  413.                     return null;  // top level has no parent
  414.             }
  415.  
  416.     },
  417.  
  418.     getChildObject: function(node, index, previousSibling)
  419.     {
  420.         if (isSourceElement(node))
  421.         {
  422.             if (index == 0)
  423.                 return this.getElementSourceText(node);
  424.         }
  425.         else if (previousSibling)
  426.         {
  427.             return this.findNextSibling(previousSibling);
  428.         }
  429.         else
  430.         {
  431.             if (index == 0 && node.contentDocument)
  432.                 return node.contentDocument.documentElement;
  433.             else if (Firebug.showWhitespaceNodes)
  434.                 return node.childNodes[index];
  435.             else
  436.             {
  437.                 var childIndex = 0;
  438.                 for (var child = node.firstChild; child; child = child.nextSibling)
  439.                 {
  440.                     if (!this.isWhitespaceText(child) && childIndex++ == index)
  441.                         return child;
  442.                 }
  443.             }
  444.         }
  445.  
  446.         return null;
  447.     },
  448.  
  449.     isWhitespaceText: function(node)
  450.     {
  451.         if (node instanceof HTMLAppletElement)
  452.             return false;
  453.         return node.nodeType == 3 && isWhitespace(node.nodeValue);
  454.     },
  455.  
  456.     findNextSibling: function (node)
  457.     {
  458.         if (Firebug.showWhitespaceNodes)
  459.             return node.nextSibling;
  460.         else
  461.         {
  462.             for (var child = node.nextSibling; child; child = child.nextSibling)
  463.             {
  464.                 if (!this.isWhitespaceText(child))
  465.                     return child;
  466.             }
  467.         }
  468.     },
  469.  
  470.     isSourceElement: function(element)
  471.     {
  472.         var tag = element.localName.toLowerCase();
  473.         return tag == "script" || tag == "link" || tag == "style"
  474.             || (tag == "link" && element.getAttribute("rel") == "stylesheet");
  475.     },
  476.  
  477.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  478.     // Events
  479.  
  480.     onMutateAttr: function(event)
  481.     {
  482.         var target = event.target;
  483.         if (target.firebugIgnore)
  484.             return;
  485.  
  486.         var attrChange = event.attrChange;
  487.         var attrName = event.attrName;
  488.         var newValue = event.newValue;
  489.  
  490.         this.context.delay(function()
  491.         {
  492.             this.mutateAttr(target, attrChange, attrName, newValue);
  493.         }, this);
  494.     },
  495.  
  496.     onMutateText: function(event)
  497.     {
  498.         var target = event.target;
  499.         var parent = target.parentNode;
  500.  
  501.         var newValue = event.newValue;
  502.  
  503.         this.context.delay(function()
  504.         {
  505.             this.mutateText(target, parent, newValue);
  506.         }, this);
  507.     },
  508.  
  509.     onMutateNode: function(event)
  510.     {
  511.         var target = event.target;
  512.         if (target.firebugIgnore)
  513.             return;
  514.  
  515.         var parent = event.relatedNode;
  516.         var removal = event.type == "DOMNodeRemoved";
  517.         var nextSibling = removal ? null : this.findNextSibling(target);
  518.  
  519.         this.context.delay(function()
  520.         {
  521.             try
  522.             {
  523.                  this.mutateNode(target, parent, nextSibling, removal);
  524.             }
  525.             catch (exc)
  526.             {
  527.             }
  528.         }, this);
  529.     },
  530.  
  531.     onClick: function(event)
  532.     {
  533.         if (isLeftClick(event) && event.detail == 2)
  534.         {
  535.             if (getAncestorByClass(event.target, "nodeTag"))
  536.             {
  537.                 var node = Firebug.getRepObject(event.target);
  538.                 this.editNode(node);
  539.             }
  540.         }
  541.     },
  542.  
  543.     onMouseDown: function(event)
  544.     {
  545.         if (!isLeftClick(event))
  546.             return;
  547.  
  548.         if (getAncestorByClass(event.target, "nodeTag"))
  549.         {
  550.             var node = Firebug.getRepObject(event.target);
  551.             this.noScrollIntoView = true;
  552.             this.select(node);
  553.             delete this.noScrollIntoView;
  554.             this.ioBox.expandObject(node);
  555.         }
  556.     },
  557.  
  558.     onKeyPress: function(event)
  559.     {
  560.         if (this.editing || isControl(event) || isShift(event))
  561.             return;
  562.  
  563.         if (event.keyCode == KeyEvent.DOM_VK_UP)
  564.             this.selectNodeBy("up");
  565.         else if (event.keyCode == KeyEvent.DOM_VK_DOWN)
  566.             this.selectNodeBy("down");
  567.         else if (event.keyCode == KeyEvent.DOM_VK_LEFT)
  568.             this.selectNodeBy("left");
  569.         else if (event.keyCode == KeyEvent.DOM_VK_RIGHT)
  570.             this.selectNodeBy("right");
  571.         else
  572.             return;
  573.  
  574.         cancelEvent(event);
  575.     },
  576.  
  577.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  578.     // extends Panel
  579.  
  580.     name: "html",
  581.     searchable: true,
  582.     dependents: ["css", "layout", "dom", "domSide", "watch"],
  583.     inspectorHistory: new Array(5),
  584.  
  585.     initialize: function()
  586.     {
  587.         this.onMutateText = bind(this.onMutateText, this);
  588.         this.onMutateAttr = bind(this.onMutateAttr, this);
  589.         this.onMutateNode = bind(this.onMutateNode, this);
  590.         this.onClick = bind(this.onClick, this);
  591.         this.onMouseDown = bind(this.onMouseDown, this);
  592.         this.onKeyPress = bind(this.onKeyPress, this);
  593.  
  594.         Firebug.Panel.initialize.apply(this, arguments);
  595.     },
  596.  
  597.     destroy: function(state)
  598.     {
  599.         persistObjects(this, state);
  600.  
  601.         Firebug.Panel.destroy.apply(this, arguments);
  602.     },
  603.  
  604.     initializeNode: function(oldPanelNode)
  605.     {
  606.         this.panelNode.addEventListener("click", this.onClick, false);
  607.         this.panelNode.addEventListener("mousedown", this.onMouseDown, false);
  608.     },
  609.  
  610.     destroyNode: function()
  611.     {
  612.         this.panelNode.removeEventListener("click", this.onClick, false);
  613.         this.panelNode.removeEventListener("mousedown", this.onMouseDown, false);
  614.         this.panelNode.ownerDocument.removeEventListener("keypress", this.onKeyPress, true);
  615.  
  616.         if (this.ioBox)
  617.         {
  618.             this.ioBox.destroy();
  619.             delete this.ioBox;
  620.         }
  621.     },
  622.  
  623.     show: function(state)
  624.     {
  625.         this.showToolbarButtons("fbHTMLButtons", true);
  626.  
  627.         if (!this.ioBox)
  628.             this.ioBox = new InsideOutBox(this, this.panelNode);
  629.  
  630.         this.panelNode.ownerDocument.addEventListener("keypress", this.onKeyPress, true);
  631.  
  632.         if (this.context.loaded)
  633.         {
  634.             if (!this.context.attachedMutation)
  635.             {
  636.                 this.context.attachedMutation = true;
  637.  
  638.                 iterateWindows(this.context.window, bind(function(win)
  639.                 {
  640.                     var doc = win.document;
  641.                     doc.addEventListener("DOMAttrModified", this.onMutateAttr, false);
  642.                     doc.addEventListener("DOMCharacterDataModified", this.onMutateText, false);
  643.                     doc.addEventListener("DOMNodeInserted", this.onMutateNode, false);
  644.                     doc.addEventListener("DOMNodeRemoved", this.onMutateNode, false);
  645.                 }, this));
  646.             }
  647.  
  648.             restoreObjects(this, state);
  649.         }
  650.     },
  651.  
  652.     hide: function()
  653.     {
  654.         this.showToolbarButtons("fbHTMLButtons", false);
  655.         delete this.infoTipURL;  // clear the state that is tracking the infotip so it is reset after next show()
  656.         this.panelNode.ownerDocument.removeEventListener("keypress", this.onKeyPress, true);
  657.     },
  658.  
  659.     watchWindow: function(win)
  660.     {
  661.         if (this.context.window && this.context.window != win) // then I guess we are an embedded window
  662.         {
  663.             var htmlPanel = this;
  664.             iterateWindows(this.context.window, function(subwin)
  665.             {
  666.                 if (win == subwin)
  667.                 {
  668.                     htmlPanel.mutateDocumentEmbedded(win, false);
  669.                 }
  670.             });
  671.  
  672.         }
  673.         if (this.context.attachedMutation)
  674.         {
  675.             var doc = win.document;
  676.             doc.addEventListener("DOMAttrModified", this.onMutateAttr, false);
  677.             doc.addEventListener("DOMCharacterDataModified", this.onMutateText, false);
  678.             doc.addEventListener("DOMNodeInserted", this.onMutateNode, false);
  679.             doc.addEventListener("DOMNodeRemoved", this.onMutateNode, false);
  680.         }
  681.     },
  682.  
  683.     unwatchWindow: function(win)
  684.     {
  685.         if (this.context.window && this.context.window != win) // then I guess we are an embedded window
  686.         {
  687.             var htmlPanel = this;
  688.             iterateWindows(this.context.window, function(subwin)
  689.             {
  690.                 if (win == subwin)
  691.                 {
  692.                     htmlPanel.mutateDocumentEmbedded(win, true);
  693.                 }
  694.             });
  695.  
  696.         }
  697.         var doc = win.document;
  698.         doc.removeEventListener("DOMAttrModified", this.onMutateAttr, false);
  699.         doc.removeEventListener("DOMCharacterDataModified", this.onMutateText, false);
  700.         doc.removeEventListener("DOMNodeInserted", this.onMutateNode, false);
  701.         doc.removeEventListener("DOMNodeRemoved", this.onMutateNode, false);
  702.     },
  703.  
  704.     mutateDocumentEmbedded: function(win, remove)
  705.     {
  706.         // document.documentElement    Returns the Element that is a direct child of document. For HTML documents, this normally the HTML element.
  707.         var target = win.document.documentElement;
  708.         var parent = win.frameElement;
  709.         var nextSibling = this.findNextSibling(target);
  710.         this.mutateNode(target, parent, nextSibling, remove);
  711.     },
  712.  
  713.     supportsObject: function(object)
  714.     {
  715.         if (object instanceof Element || object instanceof Text || object instanceof CDATASection)
  716.             return 2;
  717.         else if (object instanceof SourceLink && object.type == "css" && !reCSS.test(object.href))
  718.             return 2;
  719.         else
  720.             return 0;
  721.     },
  722.  
  723.     updateOption: function(name, value)
  724.     {
  725.         var viewOptionNames = {showCommentNodes:1, showWhitespaceNodes:1 , showFullTextNodes:1};
  726.         if (name in viewOptionNames)
  727.         {
  728.             this.resetSearch();
  729.             clearNode(this.panelNode);
  730.             if (this.ioBox)
  731.                 this.ioBox.destroy();
  732.  
  733.             this.ioBox = new InsideOutBox(this, this.panelNode);
  734.             this.ioBox.select(this.selection, true, true);
  735.         }
  736.     },
  737.  
  738.     updateSelection: function(object)
  739.     {
  740.         if (this.ioBox.sourceRow)
  741.             this.ioBox.sourceRow.removeAttribute("exeLine");
  742.  
  743.         if (object instanceof SourceLink) // && object.type == "css" and !reCSS(object.href) by supports
  744.          {
  745.              var sourceLink = object;
  746.              var stylesheet = getStyleSheetByHref(sourceLink.href, this.context);
  747.              if (stylesheet)
  748.              {
  749.                 var ownerNode = stylesheet.ownerNode;
  750.                 if (ownerNode)
  751.                 {
  752.                     var objectbox = this.ioBox.select(ownerNode, true, true, this.noScrollIntoView);
  753.  
  754.                     // XXXjjb seems like this could be bad for errors at the end of long files
  755.                     //
  756.                     var sourceRow = FBL.getElementByClass(objectbox, "sourceRow"); // first source row in style
  757.                     for (var lineNo = 1; lineNo < sourceLink.line; lineNo++)
  758.                     {
  759.                         if (!sourceRow) break;
  760.                         sourceRow = FBL.getNextByClass(sourceRow,  "sourceRow");
  761.                     }
  762.                     if (sourceRow)
  763.                     {
  764.                         this.ioBox.sourceRow = sourceRow;
  765.                         this.ioBox.sourceRow.setAttribute("exeLine", "true");
  766.                         scrollIntoCenterView(sourceRow);
  767.                         this.ioBox.selectObjectBox(sourceRow, false);  // sourceRow isn't an objectBox, but the function should work anyway...
  768.                     }
  769.                 }
  770.             }
  771.         }
  772.         else if (Firebug.Inspector.inspecting)
  773.         {
  774.             this.ioBox.highlight(object);
  775.         }
  776.         else
  777.         {
  778.             this.ioBox.select(object, true, false, this.noScrollIntoView);
  779.             this.inspectorHistory.unshift(object);
  780.             if (this.inspectorHistory.length > 5)
  781.                 this.inspectorHistory.pop();
  782.         }
  783.     },
  784.  
  785.     stopInspecting: function(object, cancelled)
  786.     {
  787.         if (object != this.inspectorHistory)
  788.         {
  789.             // Manage history of selection for later access in the command line.
  790.             this.inspectorHistory.unshift(object);
  791.             if (this.inspectorHistory.length > 5)
  792.                 this.inspectorHistory.pop();            
  793.  
  794.         }
  795.  
  796.         this.ioBox.highlight(null);
  797.  
  798.         if (!cancelled)
  799.             this.ioBox.select(object, true);
  800.     },
  801.  
  802.     search: function(text)
  803.     {
  804.         var search;
  805.         if (text == this.searchText && this.lastSearch)
  806.             search = this.lastSearch;
  807.         else
  808.         {
  809.             var doc = this.context.window.document;
  810.             search = this.lastSearch = new NodeSearch(text, doc, this.panelNode, this.ioBox);
  811.         }
  812.  
  813.         var loopAround = search.find();
  814.         if (loopAround)
  815.         {
  816.             this.resetSearch();
  817.             this.search(text);
  818.         }
  819.  
  820.         return !search.noMatch;
  821.     },
  822.  
  823.     getDefaultSelection: function()
  824.     {
  825.         try
  826.         {
  827.             var doc = this.context.window.document;
  828.             return doc.body ? doc.body : getPreviousElement(doc.documentElement.lastChild);
  829.         }
  830.         catch (exc)
  831.         {
  832.             return null;
  833.         }
  834.     },
  835.  
  836.     getObjectPath: function(element)
  837.     {
  838.         var path = [];
  839.         for (; element; element = this.getParentObject(element))
  840.             path.push(element);
  841.  
  842.         return path;
  843.     },
  844.  
  845.     getPopupObject: function(target)
  846.     {
  847.         return Firebug.getRepObject(target);
  848.     },
  849.  
  850.     getTooltipObject: function(target)
  851.     {
  852.         return null;
  853.     },
  854.  
  855.     getOptionsMenuItems: function()
  856.     {
  857.         return [
  858.             optionMenu("ShowFullText", "showFullTextNodes"),
  859.             optionMenu("ShowWhitespace", "showWhitespaceNodes"),
  860.             optionMenu("ShowComments", "showCommentNodes"),
  861.             "-",
  862.             optionMenu("HighlightMutations", "highlightMutations"),
  863.             optionMenu("ExpandMutations", "expandMutations"),
  864.             optionMenu("ScrollToMutations", "scrollToMutations")
  865.         ];
  866.     },
  867.  
  868.     getContextMenuItems: function(node, target)
  869.     {
  870.         if (!node)
  871.             return null;
  872.  
  873.         var items = [];
  874.  
  875.         if (node && node.nodeType == 1)
  876.         {
  877.             items.push(
  878.                 "-",
  879.                 {label: "NewAttribute", command: bindFixed(this.editNewAttribute, this, node) }
  880.             );
  881.  
  882.             var attrBox = getAncestorByClass(target, "nodeAttr");
  883.             if (getAncestorByClass(target, "nodeAttr"))
  884.             {
  885.                 var attrName = attrBox.childNodes[1].textContent;
  886.  
  887.                 items.push(
  888.                     {label: $STRF("EditAttribute", [attrName]), nol10n: true,
  889.                         command: bindFixed(this.editAttribute, this, node, attrName) },
  890.                     {label: $STRF("DeleteAttribute", [attrName]), nol10n: true,
  891.                         command: bindFixed(this.deleteAttribute, this, node, attrName) }
  892.                 );
  893.             }
  894.  
  895.             if (!( nonEditableTags.hasOwnProperty(node.localName) ))
  896.             {
  897.                 items.push(
  898.                     "-",
  899.                     {label: "EditElement", command: bindFixed(this.editNode, this, node) },
  900.                     {label: "DeleteElement", command: bindFixed(this.deleteNode, this, node) }
  901.                 );
  902.             }
  903.  
  904.         }
  905.         else
  906.         {
  907.             items.push(
  908.                 "-",
  909.                 {label: "EditNode", command: bindFixed(this.editNode, this, node) },
  910.                 {label: "DeleteNode", command: bindFixed(this.deleteNode, this, node) }
  911.             );
  912.         }
  913.  
  914.         return items;
  915.     },
  916.  
  917.     showInfoTip: function(infoTip, target, x, y)
  918.     {
  919.         if (!hasClass(target, "nodeValue"))
  920.             return;
  921.  
  922.         var targetNode = Firebug.getRepObject(target);
  923.         if (targetNode && targetNode.nodeType == 1 && targetNode.localName.toUpperCase() == "IMG")
  924.         {
  925.             var url = targetNode.src;
  926.             if (url == this.infoTipURL) // This state cleared in hide()
  927.                 return true;
  928.  
  929.             this.infoTipURL = url;
  930.             return Firebug.InfoTip.populateImageInfoTip(infoTip, url);
  931.         }
  932.     },
  933.  
  934.     getEditor: function(target, value)
  935.     {
  936.         if (hasClass(target, "nodeName") || hasClass(target, "nodeValue") || hasClass(target, "nodeBracket"))
  937.         {
  938.             if (!this.attrEditor)
  939.                 this.attrEditor = new AttributeEditor(this.document);
  940.  
  941.             return this.attrEditor;
  942.         }
  943.         else if (hasClass(target, "nodeText"))
  944.         {
  945.             // XXXjoe Implement special text node editor
  946.             if (!this.textEditor)
  947.                 this.textEditor = new AttributeEditor(this.document);
  948.  
  949.             return this.textEditor;
  950.         }
  951.     }
  952. });
  953.  
  954. // ************************************************************************************************
  955.  
  956. Firebug.HTMLPanel.CompleteElement = domplate(FirebugReps.Element,
  957. {
  958.     tag:
  959.         DIV({class: "nodeBox open $object|getHidden repIgnore", _repObject: "$object"},
  960.             DIV({class: "nodeLabel"},
  961.                 SPAN({class: "nodeLabelBox repTarget repTarget"},
  962.                     "<",
  963.                     SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  964.                     FOR("attr", "$object|attrIterator", AttrTag),
  965.                     SPAN({class: "nodeBracket"}, ">")
  966.                 )
  967.             ),
  968.             DIV({class: "nodeChildBox"},
  969.                 FOR("child", "$object|childIterator",
  970.                     TAG("$child|getNodeTag", {object: "$child"})
  971.                 )
  972.             ),
  973.             DIV({class: "nodeCloseLabel"},
  974.                 "</",
  975.                 SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  976.                 ">"
  977.              )
  978.         ),
  979.  
  980.     getNodeTag: function(node)
  981.     {
  982.         return getNodeTag(node, true);
  983.     },
  984.  
  985.     childIterator: function(node)
  986.     {
  987.         if (node.contentDocument)
  988.             return [node.contentDocument.documentElement];
  989.  
  990.         if (Firebug.showWhitespaceNodes)
  991.             return cloneArray(node.childNodes);
  992.         else
  993.         {
  994.             var nodes = [];
  995.             for (var child = node.firstChild; child; child = child.nextSibling)
  996.             {
  997.                 if (child.nodeType != 3 || !isWhitespaceText(child))
  998.                     nodes.push(child);
  999.             }
  1000.             return nodes;
  1001.         }
  1002.     }
  1003. });
  1004.  
  1005. Firebug.HTMLPanel.SoloElement = domplate(Firebug.HTMLPanel.CompleteElement,
  1006. {
  1007.     tag:
  1008.         DIV({class: "soloElement", onmousedown: "$onMouseDown"},
  1009.             Firebug.HTMLPanel.CompleteElement.tag
  1010.         ),
  1011.  
  1012.     onMouseDown: function(event)
  1013.     {
  1014.         for (var child = event.target; child; child = child.parentNode)
  1015.         {
  1016.             if (child.repObject)
  1017.             {
  1018.                 var panel = Firebug.getElementPanel(child);
  1019.                 panel.context.chrome.select(child.repObject);
  1020.                 break;
  1021.             }
  1022.         }
  1023.     }
  1024. });
  1025.  
  1026. Firebug.HTMLPanel.Element = domplate(FirebugReps.Element,
  1027. {
  1028.     tag:
  1029.         DIV({class: "nodeBox containerNodeBox $object|getHidden repIgnore", _repObject: "$object"},
  1030.             DIV({class: "nodeLabel"},
  1031.                 IMG({class: "twisty"}),
  1032.                 SPAN({class: "nodeLabelBox repTarget"},
  1033.                     "<",
  1034.                     SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  1035.                     FOR("attr", "$object|attrIterator", AttrTag),
  1036.                     SPAN({class: "nodeBracket editable insertBefore"}, ">")
  1037.                 )
  1038.             ),
  1039.             DIV({class: "nodeChildBox"}),
  1040.             DIV({class: "nodeCloseLabel"},
  1041.                 SPAN({class: "nodeCloseLabelBox repTarget"},
  1042.                     "</",
  1043.                     SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  1044.                     ">"
  1045.                 )
  1046.              )
  1047.         )
  1048. });
  1049.  
  1050. Firebug.HTMLPanel.TextElement = domplate(FirebugReps.Element,
  1051. {
  1052.     tag:
  1053.         DIV({class: "nodeBox textNodeBox $object|getHidden repIgnore", _repObject: "$object"},
  1054.             DIV({class: "nodeLabel"},
  1055.                 SPAN({class: "nodeLabelBox repTarget"},
  1056.                     "<",
  1057.                     SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  1058.                     FOR("attr", "$object|attrIterator", AttrTag),
  1059.                     SPAN({class: "nodeBracket editable insertBefore"}, ">"),
  1060.                     SPAN({class: "nodeText editable"}, "$object|getNodeText"),
  1061.                     "</",
  1062.                     SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  1063.                     ">"
  1064.                 )
  1065.             )
  1066.         )
  1067. });
  1068.  
  1069. Firebug.HTMLPanel.EmptyElement = domplate(FirebugReps.Element,
  1070. {
  1071.     tag:
  1072.         DIV({class: "nodeBox emptyNodeBox $object|getHidden repIgnore", _repObject: "$object"},
  1073.             DIV({class: "nodeLabel"},
  1074.                 SPAN({class: "nodeLabelBox repTarget"},
  1075.                     "<",
  1076.                     SPAN({class: "nodeTag"}, "$object.localName|toLowerCase"),
  1077.                     FOR("attr", "$object|attrIterator", AttrTag),
  1078.                     SPAN({class: "nodeBracket editable insertBefore"}, "/>")
  1079.                 )
  1080.             )
  1081.         )
  1082. });
  1083.  
  1084. Firebug.HTMLPanel.AttrNode = domplate(FirebugReps.Element,
  1085. {
  1086.     tag: AttrTag
  1087. }),
  1088.  
  1089. Firebug.HTMLPanel.TextNode = domplate(FirebugReps.Element,
  1090. {
  1091.     tag:
  1092.         DIV({class: "nodeBox", _repObject: "$object"},
  1093.             SPAN({class: "nodeText editable"}, "$object.nodeValue")
  1094.         )
  1095. }),
  1096.  
  1097. Firebug.HTMLPanel.CDATANode = domplate(FirebugReps.Element,
  1098. {
  1099.     tag:
  1100.         DIV({class: "nodeBox", _repObject: "$object"},
  1101.             "<![CDATA[",
  1102.             SPAN({class: "nodeText editable"}, "$object.nodeValue"),
  1103.             "]]>"
  1104.         )
  1105. }),
  1106.  
  1107. Firebug.HTMLPanel.CommentNode = domplate(FirebugReps.Element,
  1108. {
  1109.     tag:
  1110.         DIV({class: "nodeBox", _repObject: "$object"},
  1111.             DIV({class: "nodeComment editable"},
  1112.                 "<!--$object.nodeValue-->"
  1113.             )
  1114.         )
  1115. });
  1116.  
  1117. // ************************************************************************************************
  1118. // AttributeEditor
  1119.  
  1120. function AttributeEditor(doc)
  1121. {
  1122.     this.initializeInline(doc);
  1123. }
  1124.  
  1125. AttributeEditor.prototype = domplate(Firebug.InlineEditor.prototype,
  1126. {
  1127.     saveEdit: function(target, value, previousValue)
  1128.     {
  1129.         var element = Firebug.getRepObject(target);
  1130.         if (!element)
  1131.             return;
  1132.  
  1133.         target.innerHTML = escapeHTML(value);
  1134.  
  1135.         if (hasClass(target, "nodeName"))
  1136.         {
  1137.             if (value != previousValue)
  1138.                 element.removeAttribute(previousValue);
  1139.  
  1140.             if (value)
  1141.             {
  1142.                 var attrValue = getNextByClass(target, "nodeValue").textContent;
  1143.                 element.setAttribute(value, attrValue);
  1144.             }
  1145.             else
  1146.                 element.removeAttribute(value);
  1147.         }
  1148.         else if (hasClass(target, "nodeValue"))
  1149.         {
  1150.             var attrName = getPreviousByClass(target, "nodeName").textContent;
  1151.             element.setAttribute(attrName, value);
  1152.         }
  1153.         else if (hasClass(target, "nodeText"))
  1154.         {
  1155.             if (element instanceof Element)
  1156.                 element.innerHTML = value;
  1157.             else
  1158.                 element.nodeValue = value;
  1159.         }
  1160.  
  1161.         //this.panel.markChange();
  1162.     },
  1163.  
  1164.     advanceToNext: function(target, charCode)
  1165.     {
  1166.         if (charCode == 61 && hasClass(target, "nodeName"))
  1167.             return true;
  1168.     },
  1169.  
  1170.     insertNewRow: function(target, insertWhere)
  1171.     {
  1172.         var emptyAttr = {nodeName: "", nodeValue: ""};
  1173.         var sibling = insertWhere == "before" ? target.previousSibling : target;
  1174.  
  1175.         return AttrTag.insertAfter({attr: emptyAttr}, sibling);
  1176.     },
  1177.  
  1178.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1179.  
  1180.     getAutoCompleteRange: function(value, offset)
  1181.     {
  1182.     },
  1183.  
  1184.     getAutoCompleteList: function(preExpr, expr, postExpr)
  1185.     {
  1186.     }
  1187. });
  1188.  
  1189. // ************************************************************************************************
  1190. // HTMLEditor
  1191.  
  1192. function HTMLEditor(doc)
  1193. {
  1194.     this.box = this.tag.replace({}, doc, this);
  1195.     this.input = this.box.firstChild;
  1196.  
  1197.     this.multiLine = true;
  1198.     this.tabNavigation = false;
  1199.     this.arrowCompletion = false;
  1200. }
  1201.  
  1202. HTMLEditor.prototype = domplate(Firebug.BaseEditor,
  1203. {
  1204.     tag: DIV(
  1205.         TEXTAREA({class: "htmlEditor fullPanelEditor", oninput: "$onInput"})
  1206.     ),
  1207.  
  1208.     getValue: function()
  1209.     {
  1210.         return this.input.value;
  1211.     },
  1212.  
  1213.     setValue: function(value)
  1214.     {
  1215.         return this.input.value = value;
  1216.     },
  1217.  
  1218.     show: function(target, panel, value, textSize, targetSize)
  1219.     {
  1220.         this.target = target;
  1221.         this.panel = panel;
  1222.         this.editingElements = [target.repObject, null];
  1223.  
  1224.         this.panel.panelNode.appendChild(this.box);
  1225.  
  1226.         this.input.value = value;
  1227.         this.input.focus();
  1228.  
  1229.         var command = this.panel.context.chrome.$("cmd_toggleHTMLEditing");
  1230.         command.setAttribute("checked", true);
  1231.     },
  1232.  
  1233.     hide: function()
  1234.     {
  1235.         var chrome = this.panel.context.chrome;
  1236.         if (!chrome)
  1237.             chrome = FirebugChrome;
  1238.  
  1239.         var command = chrome.$("cmd_toggleHTMLEditing");
  1240.         command.setAttribute("checked", false);
  1241.  
  1242.         this.panel.panelNode.removeChild(this.box);
  1243.  
  1244.         delete this.editingElements;
  1245.         delete this.target;
  1246.         delete this.panel;
  1247.     },
  1248.  
  1249.     saveEdit: function(target, value, previousValue)
  1250.     {
  1251.         // Remove all of the nodes in the last range we created, except for
  1252.         // the first one, because setOuterHTML will replace it
  1253.         var first = this.editingElements[0], last = this.editingElements[1];
  1254.         if (last && last != first)
  1255.         {
  1256.             for (var child = first.nextSibling; child;)
  1257.             {
  1258.                 var next = child.nextSibling;
  1259.                 child.parentNode.removeChild(child);
  1260.                 if (child == last)
  1261.                     break;
  1262.                 else
  1263.                     child = next;
  1264.             }
  1265.         }
  1266.  
  1267.         // Make sure that we create at least one node here, even if it's just
  1268.         // an empty space, because this code depends on having something to replace
  1269.         if (!value)
  1270.             value = " ";
  1271.  
  1272.         if (this.innerEditMode)
  1273.             this.editingElements[0].innerHTML = value;
  1274.         else
  1275.             this.editingElements = setOuterHTML(this.editingElements[0], value);
  1276.     },
  1277.  
  1278.     endEditing: function()
  1279.     {
  1280.         //this.panel.markChange();
  1281.         return true;
  1282.     },
  1283.  
  1284.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1285.  
  1286.     onInput: function()
  1287.     {
  1288.         Firebug.Editor.update();
  1289.     }
  1290. });
  1291.  
  1292. // ************************************************************************************************
  1293. // Local Helpers
  1294.  
  1295. function getNodeTag(node, expandAll)
  1296. {
  1297.     if (node instanceof Element)
  1298.     {
  1299.         if (node instanceof HTMLAppletElement)
  1300.             return Firebug.HTMLPanel.EmptyElement.tag;
  1301.         else if (node.firebugIgnore)
  1302.             return null;
  1303.         else if (isContainerElement(node))
  1304.             return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag;
  1305.         else if (isEmptyElement(node))
  1306.             return Firebug.HTMLPanel.EmptyElement.tag;
  1307.         else if (isPureText(node))
  1308.             return Firebug.HTMLPanel.TextElement.tag;
  1309.         else
  1310.             return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag;
  1311.     }
  1312.     else if (node instanceof Text)
  1313.         return Firebug.HTMLPanel.TextNode.tag;
  1314.     else if (node instanceof CDATASection)
  1315.         return Firebug.HTMLPanel.CDATANode.tag;
  1316.     else if (node instanceof Comment && (Firebug.showCommentNodes || expandAll))
  1317.         return Firebug.HTMLPanel.CommentNode.tag;
  1318.     else if (node instanceof SourceText)
  1319.         return FirebugReps.SourceText.tag;
  1320.     else
  1321.         return FirebugReps.Nada.tag;
  1322. }
  1323.  
  1324. function getNodeBoxTag(nodeBox)
  1325. {
  1326.     var re = /([^\s]+)NodeBox/;
  1327.     var m = re.exec(nodeBox.className);
  1328.     if (!m)
  1329.         return null;
  1330.  
  1331.     var nodeBoxType = m[1];
  1332.     if (nodeBoxType == "container")
  1333.         return Firebug.HTMLPanel.Element.tag;
  1334.     else if (nodeBoxType == "text")
  1335.         return Firebug.HTMLPanel.TextElement.tag;
  1336.     else if (nodeBoxType == "empty")
  1337.         return Firebug.HTMLPanel.EmptyElement.tag;
  1338. }
  1339.  
  1340. function getSourceHref(element)
  1341. {
  1342.     var tag = element.localName.toLowerCase();
  1343.     if (tag == "script" && element.src)
  1344.         return element.src;
  1345.     else if (tag == "link")
  1346.         return element.href;
  1347.     else
  1348.         return null;
  1349. }
  1350.  
  1351. function getSourceText(element)
  1352. {
  1353.     var tag = element.localName.toLowerCase();
  1354.     if (tag == "script" && !element.src)
  1355.         return element.textContent;
  1356.     else if (tag == "style")
  1357.         return element.textContent;
  1358.     else
  1359.         return null;
  1360. }
  1361.  
  1362. function isContainerElement(element)
  1363. {
  1364.     var tag = element.localName.toLowerCase();
  1365.     switch (tag)
  1366.     {
  1367.         case "script":
  1368.         case "style":
  1369.         case "iframe":
  1370.         case "frame":
  1371.         case "tabbrowser":
  1372.         case "browser":
  1373.             return true;
  1374.         case "link":
  1375.             return element.getAttribute("rel") == "stylesheet";
  1376.     }
  1377.     return false;
  1378. }
  1379.  
  1380. function isPureText(element)
  1381. {
  1382.     for (var child = element.firstChild; child; child = child.nextSibling)
  1383.     {
  1384.         if (child.nodeType == 1)
  1385.             return false;
  1386.     }
  1387.     return true;
  1388. }
  1389.  
  1390. // Duplicate of HTMLPanel.prototype isWhitespaceText
  1391. function isWhitespaceText(node)
  1392. {
  1393.     if (node instanceof HTMLAppletElement)
  1394.         return false;
  1395.     return node.nodeType == 3 && isWhitespace(node.nodeValue);
  1396. }
  1397.  
  1398. // Duplicate of HTMLPanel.prototype TODO: create a namespace for all of these functions so
  1399. // they can be called outside of this file.
  1400. function isSourceElement(element)
  1401. {
  1402.     var tag = element.localName.toLowerCase();
  1403.     return tag == "script" || tag == "link" || tag == "style"
  1404.         || (tag == "link" && element.getAttribute("rel") == "stylesheet");
  1405. }
  1406.  
  1407. function isEmptyElement(element)
  1408. {
  1409.     // XXXjjb the commented code causes issues 48, 240, and 244. I think the lines should be deleted.
  1410.     // If the DOM has whitespace children, then the element is not empty even if
  1411.     // we decide not to show the whitespace in the UI.
  1412. //    if (Firebug.showWhitespaceNodes)
  1413. //    {
  1414.         return !element.firstChild;
  1415. //    }
  1416. //    else
  1417. //    {
  1418. //        for (var child = element.firstChild; child; child = child.nextSibling)
  1419. //        {
  1420. //            if (!isWhitespaceText(child))
  1421. //                return false;
  1422. //        }
  1423. //    }
  1424. //    return true;
  1425. }
  1426.  
  1427. function findNextSibling(node)
  1428. {
  1429.     if (Firebug.showWhitespaceNodes)
  1430.         return node.nextSibling;
  1431.     else
  1432.     {
  1433.         for (var child = node.nextSibling; child; child = child.nextSibling)
  1434.         {
  1435.             if (!isWhitespaceText(child))
  1436.                 return child;
  1437.         }
  1438.     }
  1439. }
  1440.  
  1441. function findNodeAttrBox(objectNodeBox, attrName)
  1442. {
  1443.     var child = objectNodeBox.firstChild.lastChild.firstChild;
  1444.     for (; child; child = child.nextSibling)
  1445.     {
  1446.         if (hasClass(child, "nodeAttr") && child.childNodes[1].firstChild
  1447.             && child.childNodes[1].firstChild.nodeValue == attrName)
  1448.         {
  1449.             return child;
  1450.         }
  1451.     }
  1452. }
  1453.  
  1454. function getTextElementTextBox(nodeBox)
  1455. {
  1456.     var nodeLabelBox = nodeBox.firstChild.lastChild;
  1457.     return getChildByClass(nodeLabelBox, "nodeText");
  1458. }
  1459.  
  1460.  
  1461. // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  1462.  
  1463. function NodeSearch(text, doc, panelNode, ioBox)
  1464. {
  1465.     var walker;
  1466.     var re = new RegExp(text, "i");
  1467.     var matchCount = 0;
  1468.  
  1469.     this.find = function()
  1470.     {
  1471.         var match = this.findNextMatch();
  1472.         if (match)
  1473.         {
  1474.             this.lastMatch = match;
  1475.             ++matchCount;
  1476.  
  1477.             var node = match[0];
  1478.             var nodeBox = this.openToNode(node, match[2]);
  1479.  
  1480.             setTimeout(bindFixed(function()
  1481.             {
  1482.                 this.selectNodeText(nodeBox, node, text, match[4]);
  1483.             }, this));
  1484.         }
  1485.         else if (matchCount)
  1486.             return true;
  1487.         else
  1488.             this.noMatch = true;
  1489.     };
  1490.  
  1491.     this.reset = function()
  1492.     {
  1493.         delete this.lastMatch;
  1494.         delete this.lastRange;
  1495.     };
  1496.  
  1497.     this.findNextMatch = function()
  1498.     {
  1499.         var firstTime = !walker;
  1500.         if (!walker)
  1501.             walker = doc.createTreeWalker(doc.documentElement, SHOW_ALL, null, true);
  1502.  
  1503.         var innerMatch = this.findNextInnerMatch();
  1504.         if (innerMatch)
  1505.             return innerMatch;
  1506.         else
  1507.             this.reset();
  1508.  
  1509.         var node = firstTime ? walker.root : walker.nextNode();
  1510.         for (; node; node = walker.nextNode())
  1511.         {
  1512.             if (node.nodeType == 3)
  1513.             {
  1514.                 if (isSourceElement(node.parentNode))
  1515.                     continue;
  1516.  
  1517.                 var m = re.exec(node.nodeValue);
  1518.                 if (m)
  1519.                     return [node, -1, false, m, m.index];
  1520.             }
  1521.             else if (node.nodeType == 1)
  1522.             {
  1523.                 var m = re.exec(node.nodeName);
  1524.                 if (m)
  1525.                     return [node, -1, false, m, m.index];
  1526.  
  1527.                 var attrMatch = this.findNextAttribute(node, -1);
  1528.                 if (attrMatch)
  1529.                     return attrMatch;
  1530.             }
  1531.         }
  1532.     };
  1533.  
  1534.     this.findNextAttribute = function(node, index, startWithValue)
  1535.     {
  1536.         var attrs = node.attributes;
  1537.         if (startWithValue)
  1538.         {
  1539.             var attr = attrs[index];
  1540.             m = re.exec(attr.nodeValue);
  1541.             if (m)
  1542.                 return [attr, index, true, m, m.index];
  1543.         }
  1544.  
  1545.         for (var i = index+1; i < attrs.length; ++i)
  1546.         {
  1547.             var attr = attrs[i];
  1548.  
  1549.             m = re.exec(attr.nodeName);
  1550.             if (m)
  1551.                 return [attr, i, false, m, m.index];
  1552.  
  1553.             m = re.exec(attr.nodeValue);
  1554.             if (m)
  1555.                 return [attr, i, true, m, m.index];
  1556.         }
  1557.     };
  1558.  
  1559.     this.findNextInnerMatch = function()
  1560.     {
  1561.         if (this.lastRange)
  1562.         {
  1563.             var lastMatchNode = this.lastMatch[0];
  1564.  
  1565.             var lastReMatch = this.lastMatch[3];
  1566.             var subText = lastReMatch.input.substr(lastReMatch.index+text.length);
  1567.             var m = re.exec(subText);
  1568.             if (m)
  1569.             {
  1570.                 var startIndex = this.lastMatch[4]+text.length+m.index;
  1571.                 var nodeText = this.lastRange.startContainer.nodeValue;
  1572.                 if (startIndex+text.length < nodeText.length)
  1573.                     return [lastMatchNode, this.lastMatch[1], this.lastMatch[2], m, startIndex];
  1574.             }
  1575.             else if (lastMatchNode.nodeType == 1)
  1576.                 return this.findNextAttribute(lastMatchNode, -1);
  1577.             else if (lastMatchNode.nodeType == 2)
  1578.                 return this.findNextAttribute(lastMatchNode.ownerElement, this.lastMatch[1],
  1579.                      !this.lastMatch[2]);
  1580.         }
  1581.     };
  1582.  
  1583.     this.openToNode = function(node, isValue)
  1584.     {
  1585.         if (node.nodeType == 1)
  1586.         {
  1587.             return ioBox.openToObject(node);
  1588.         }
  1589.         else if (node.nodeType == 2)
  1590.         {
  1591.             var nodeBox = this.openToNode(node.ownerElement);
  1592.             if (nodeBox)
  1593.             {
  1594.                 var attrNodeBox = findNodeAttrBox(nodeBox, node.nodeName);
  1595.                 if (isValue)
  1596.                     return getChildByClass(attrNodeBox, "nodeValue");
  1597.                 else
  1598.                     return attrNodeBox;
  1599.             }
  1600.         }
  1601.         else if (node.nodeType == 3)
  1602.         {
  1603.             var nodeBox = ioBox.openToObject(node);
  1604.             if (nodeBox)
  1605.                 return nodeBox;
  1606.             else
  1607.             {
  1608.                 var nodeBox = ioBox.openToObject(node.parentNode);
  1609.                 if (hasClass(nodeBox, "textNodeBox"))
  1610.                     nodeBox = getTextElementTextBox(nodeBox);
  1611.                 return nodeBox;
  1612.             }
  1613.         }
  1614.     };
  1615.  
  1616.     this.selectNodeText = function(nodeBox, node, text, index)
  1617.     {
  1618.         var row, range;
  1619.  
  1620.         // If we are still inside the same node as the last search, advance the range
  1621.         // to the next substring within that node
  1622.         if (nodeBox == this.lastNodeBox)
  1623.         {
  1624.             var target = this.lastRange.startContainer;
  1625.             range = this.lastRange = panelNode.ownerDocument.createRange();
  1626.             range.setStart(target, index);
  1627.             range.setEnd(target, index+text.length);
  1628.  
  1629.             row = this.lastRow;
  1630.         }
  1631.  
  1632.         if (!range)
  1633.         {
  1634.             // Search for the first instance of the string inside the node
  1635.             function findRow(node) { return node.nodeType == 1 ? node : node.parentNode; }
  1636.             var search = new TextSearch(nodeBox, findRow);
  1637.             row = this.lastRow = search.find(text);
  1638.             range = this.lastRange = search.range;
  1639.             this.lastNodeBox = nodeBox;
  1640.         }
  1641.  
  1642.         if (row)
  1643.         {
  1644.             var sel = panelNode.ownerDocument.defaultView.getSelection();
  1645.             sel.removeAllRanges();
  1646.             sel.addRange(range);
  1647.  
  1648.             scrollIntoCenterView(row, panelNode);
  1649.             return true;
  1650.         }
  1651.     };
  1652. }
  1653.  
  1654. // ************************************************************************************************
  1655.  
  1656. Firebug.registerPanel(Firebug.HTMLPanel);
  1657.  
  1658. // ************************************************************************************************
  1659.  
  1660. }});
  1661.